/**
 * 
 */
package org.swing.utility.jai.tiff;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.ImageProducer;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;

import org.w3c.dom.NodeList;

import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet;
import com.sun.media.imageio.plugins.tiff.TIFFDirectory;
import com.sun.media.imageio.plugins.tiff.TIFFField;
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
import com.sun.media.imageio.plugins.tiff.TIFFTag;
import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.ImageEncoder;
import com.sun.media.jai.codec.TIFFEncodeParam;

/**
 * @author Quynh Nhu
 * 
 */
public class IOCreateTiff {
	final static String OUTPUT_FILE_NAME = "Tesstmp";
	final static String TIFF_EXT = ".tif";
	final static String TIFF_FORMAT = "tiff";

	/**
	 * Creates a list of TIFF image files from an image file. It basically
	 * converts images of other formats to TIFF format, or a multi-page TIFF
	 * image to multiple TIFF image files.
	 * 
	 * @param imageFile
	 *            input image file
	 * @param index
	 *            an index of the page; -1 means all pages, as in a multi-page
	 *            TIFF image
	 * @return a list of TIFF image files
	 * @throws Exception
	 */
	public static List<File> createTiffFiles(File imageFile, int index)
			throws IOException {
		List<File> tiffFiles = new ArrayList<File>();

		String imageFileName = imageFile.getName();
		String imageFormat = imageFileName.substring(imageFileName
				.lastIndexOf('.') + 1);

		Iterator<ImageReader> readers = ImageIO
				.getImageReadersByFormatName(imageFormat);
		ImageReader reader = readers.next();

		if (reader == null) {
			throw new RuntimeException(
					"Need to install JAI Image I/O package.\nhttps://jai-imageio.dev.java.net");
		}

		ImageInputStream iis = ImageIO.createImageInputStream(imageFile);
		reader.setInput(iis);
		// Read the stream metadata
		// IIOMetadata streamMetadata = reader.getStreamMetadata();

		// Set up the writeParam
		TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
		tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

		// Get tif writer and set output to file
		Iterator<ImageWriter> writers = ImageIO
				.getImageWritersByFormatName(TIFF_FORMAT);
		ImageWriter writer = writers.next();

		// Read the stream metadata
		IIOMetadata streamMetadata = writer
				.getDefaultStreamMetadata(tiffWriteParam);

		int imageTotal = reader.getNumImages(true);

		for (int i = 0; i < imageTotal; i++) {
			// all if index == -1; otherwise, only index-th
			if (index == -1 || i == index) {
				// BufferedImage bi = reader.read(i);
				// IIOImage oimage = new IIOImage(bi, null,
				// reader.getImageMetadata(i));
				IIOImage oimage = reader.readAll(i,
						reader.getDefaultReadParam());
				File tiffFile = File.createTempFile(OUTPUT_FILE_NAME, TIFF_EXT);
				ImageOutputStream ios = ImageIO
						.createImageOutputStream(tiffFile);
				writer.setOutput(ios);
				writer.write(streamMetadata, oimage, tiffWriteParam);
				ios.close();
				tiffFiles.add(tiffFile);
			}
		}
		writer.dispose();
		reader.dispose();

		return tiffFiles;
	}

	/**
	 * Creates a list of TIFF image files from a list of <code>IIOImage</code>
	 * objects.
	 * 
	 * @param imageList
	 *            a list of <code>IIOImage</code> objects
	 * @param index
	 *            an index of the page; -1 means all pages
	 * @return a list of TIFF image files
	 * @throws Exception
	 */
	public static List<File> createTiffFiles(List<IIOImage> imageList, int index)
			throws IOException {
		return createTiffFiles(imageList, index, 0, 0);
	}

	public static List<File> createTiffFiles(List<IIOImage> imageList,
			int index, int dpiX, int dpiY) throws IOException {
		List<File> tiffFiles = new ArrayList<File>();

		// Set up the writeParam
		TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
		tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

		// Get tif writer and set output to file
		Iterator<ImageWriter> writers = ImageIO
				.getImageWritersByFormatName(TIFF_FORMAT);
		ImageWriter writer = writers.next();

		if (writer == null) {
			throw new RuntimeException(
					"Need to install JAI Image I/O package.\nhttps://jai-imageio.dev.java.net");
		}

		// Get the stream metadata
		IIOMetadata streamMetadata = writer
				.getDefaultStreamMetadata(tiffWriteParam);

		// all if index == -1; otherwise, only index-th
		for (IIOImage oimage : (index == -1 ? imageList : imageList.subList(
				index, index + 1))) {
			if (dpiX != 0 && dpiY != 0) {
				// Get the default image metadata.
				ImageTypeSpecifier imageType = ImageTypeSpecifier
						.createFromRenderedImage(oimage.getRenderedImage());
				IIOMetadata imageMetadata = writer.getDefaultImageMetadata(
						imageType, null);
				imageMetadata = setDPIViaAPI(imageMetadata, dpiX, dpiY);
				oimage.setMetadata(imageMetadata);
			}

			File tiffFile = File.createTempFile(OUTPUT_FILE_NAME, TIFF_EXT);
			ImageOutputStream ios = ImageIO.createImageOutputStream(tiffFile);
			writer.setOutput(ios);
			writer.write(streamMetadata, oimage, tiffWriteParam);
			ios.close();
			tiffFiles.add(tiffFile);
		}
		writer.dispose();

		return tiffFiles;
	}

	/**
	 * Set DPI using API.
	 */
	private static IIOMetadata setDPIViaAPI(IIOMetadata imageMetadata,
			int dpiX, int dpiY) throws IIOInvalidTreeException {
		// Derive the TIFFDirectory from the metadata.
		TIFFDirectory dir = TIFFDirectory.createFromMetadata(imageMetadata);

		// Get {X,Y}Resolution tags.
		BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
		TIFFTag tagXRes = base.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION);
		TIFFTag tagYRes = base.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION);

		// Create {X,Y}Resolution fields.
		TIFFField fieldXRes = new TIFFField(tagXRes, TIFFTag.TIFF_RATIONAL, 1,
				new long[][] { { dpiX, 1 } });
		TIFFField fieldYRes = new TIFFField(tagYRes, TIFFTag.TIFF_RATIONAL, 1,
				new long[][] { { dpiY, 1 } });

		// Append {X,Y}Resolution fields to directory.
		dir.addTIFFField(fieldXRes);
		dir.addTIFFField(fieldYRes);

		// Convert to metadata object and return.
		return dir.getAsMetadata();
	}

	/**
	 * Gets pixel data of an <code>IIOImage</code> object.
	 * 
	 * @param image
	 *            an <code>IIOImage</code> object
	 * @return a byte buffer of pixel data
	 * @throws Exception
	 */
	public static ByteBuffer getImageByteBuffer(IIOImage image)
			throws IOException {
		// Set up the writeParam
		TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
		tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

		// Get tif writer and set output to file
		Iterator<ImageWriter> writers = ImageIO
				.getImageWritersByFormatName(TIFF_FORMAT);
		ImageWriter writer = writers.next();

		if (writer == null) {
			throw new RuntimeException(
					"Need to install JAI Image I/O package.\nhttps://jai-imageio.dev.java.net");
		}

		// Get the stream metadata
		IIOMetadata streamMetadata = writer
				.getDefaultStreamMetadata(tiffWriteParam);

		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
		writer.setOutput(ios);
		writer.write(streamMetadata, new IIOImage(image.getRenderedImage(),
				null, null), tiffWriteParam);
		// writer.write(image.getRenderedImage());
		writer.dispose();
		// ImageIO.write(image.getRenderedImage(), "tiff", ios); // this can be
		// used in lieu of writer
		ios.seek(0);
		BufferedImage bi = ImageIO.read(ios);
		return convertImageData(bi);
	}

	/**
	 * Converts <code>BufferedImage</code> to <code>ByteBuffer</code>.
	 * 
	 * @param bi
	 *            Input image
	 * @return pixel data
	 */
	public static ByteBuffer convertImageData(BufferedImage bi) {
		byte[] pixelData = ((DataBufferByte) bi.getRaster().getDataBuffer())
				.getData();
		// return ByteBuffer.wrap(pixelData);
		ByteBuffer buf = ByteBuffer.allocateDirect(pixelData.length);
		buf.order(ByteOrder.nativeOrder());
		buf.put(pixelData);
		buf.flip();
		return buf;
	}

	/**
	 * Gets a list of <code>IIOImage</code> objects for an image file.
	 * 
	 * @param imageFile
	 *            input image file. It can be any of the supported formats,
	 *            including TIFF, JPEG, GIF, PNG, BMP, JPEG, and PDF if GPL
	 *            Ghostscript is installed
	 * @return a list of <code>IIOImage</code> objects
	 * @throws Exception
	 */
	public static List<IIOImage> getIIOImageList(File imageFile)
			throws IOException {
		File workingTiffFile = null;

		ImageReader reader = null;
		ImageInputStream iis = null;

		try {
			// convert PDF to TIFF
			if (imageFile.getName().toLowerCase().endsWith(".pdf")) {
				throw new IOException("PDF files are not currently supported.");
				// workingTiffFile = PdfUtilities.convertPdf2Tiff(imageFile);
				// imageFile = workingTiffFile;
			}

			List<IIOImage> iioImageList = new ArrayList<IIOImage>();

			String imageFileName = imageFile.getName();
			String imageFormat = imageFileName.substring(imageFileName
					.lastIndexOf('.') + 1);
			if (imageFormat.matches("(pbm|pgm|ppm)")) {
				imageFormat = "pnm";
			} else if (imageFormat.equals("jp2")) {
				imageFormat = "jpeg2000";
			}
			Iterator<ImageReader> readers = ImageIO
					.getImageReadersByFormatName(imageFormat);
			reader = readers.next();

			if (reader == null) {
				throw new RuntimeException(
						"Need to install JAI Image I/O package.\nhttps://jai-imageio.dev.java.net");
			}

			iis = ImageIO.createImageInputStream(imageFile);
			reader.setInput(iis);

			int imageTotal = reader.getNumImages(true);

			for (int i = 0; i < imageTotal; i++) {
				// IIOImage oimage = new IIOImage(reader.read(i), null,
				// reader.getImageMetadata(i));
				IIOImage oimage = reader.readAll(i,
						reader.getDefaultReadParam());
				iioImageList.add(oimage);
			}

			return iioImageList;
		} finally {
			try {
				if (iis != null) {
					iis.close();
				}
				if (reader != null) {
					reader.dispose();
				}
			} catch (Exception e) {
				// ignore
			}
			if (workingTiffFile != null && workingTiffFile.exists()) {
				workingTiffFile.delete();
			}
		}
	}

	/**
	 * Merges multiple images into one TIFF image.
	 * 
	 * @param inputImages
	 *            an array of image files
	 * @param outputTiff
	 *            the output TIFF file
	 * @throws Exception
	 */
	public static void mergeTiff(File[] inputImages, File outputTiff)
			throws IOException {
		List<IIOImage> imageList = new ArrayList<IIOImage>();

		for (int i = 0; i < inputImages.length; i++) {
			imageList.addAll(getIIOImageList(inputImages[i]));
		}

		if (imageList.isEmpty()) {
			// if no image
			return;
		}

		Iterator<ImageWriter> writers = ImageIO
				.getImageWritersByFormatName(TIFF_FORMAT);
		ImageWriter writer = writers.next();

		// Set up the writeParam
		TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
		tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

		// Get the stream metadata
		IIOMetadata streamMetadata = writer
				.getDefaultStreamMetadata(tiffWriteParam);

		ImageOutputStream ios = ImageIO.createImageOutputStream(outputTiff);
		writer.setOutput(ios);

		IIOImage firstIioImage = imageList.remove(0);
		writer.write(streamMetadata, firstIioImage, tiffWriteParam);

		int i = 1;
		for (IIOImage iioImage : imageList) {
			writer.writeInsert(i++, iioImage, tiffWriteParam);
		}
		ios.close();

		writer.dispose();
	}

	/**
	 * Reads image meta data.
	 * 
	 * @param oimage
	 * @return a map of meta data
	 */
	public static Map<String, String> readImageData(IIOImage oimage) {
		Map<String, String> dict = new HashMap<String, String>();

		IIOMetadata imageMetadata = oimage.getMetadata();
		if (imageMetadata != null) {
			IIOMetadataNode dimNode = (IIOMetadataNode) imageMetadata
					.getAsTree("javax_imageio_1.0");
			NodeList nodes = dimNode
					.getElementsByTagName("HorizontalPixelSize");
			int dpiX;
			if (nodes.getLength() > 0) {
				float dpcWidth = Float.parseFloat(nodes.item(0).getAttributes()
						.item(0).getNodeValue());
				dpiX = (int) Math.round(25.4f / dpcWidth);
			} else {
				dpiX = Toolkit.getDefaultToolkit().getScreenResolution();
			}
			dict.put("dpiX", String.valueOf(dpiX));

			nodes = dimNode.getElementsByTagName("VerticalPixelSize");
			int dpiY;
			if (nodes.getLength() > 0) {
				float dpcHeight = Float.parseFloat(nodes.item(0)
						.getAttributes().item(0).getNodeValue());
				dpiY = (int) Math.round(25.4f / dpcHeight);
			} else {
				dpiY = Toolkit.getDefaultToolkit().getScreenResolution();
			}
			dict.put("dpiY", String.valueOf(dpiY));
		}

		return dict;
	}

	public static File createImage(BufferedImage bi) {
		File tempFile = null;
		try {
			tempFile = File.createTempFile("tempImageFile", ".tif");
			createImage(bi, tempFile);
		} catch (Exception exc) {
			exc.printStackTrace();
		}
		return tempFile;
	}

	public static File createImage(BufferedImage bi, File file) {
		try {
			file.deleteOnExit();
			TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(
					Locale.US);
			tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

			// Get tif writer and set output to file
			Iterator<ImageWriter> writers = ImageIO
					.getImageWritersByFormatName("tiff");
			ImageWriter writer = writers.next();

			IIOImage image = new IIOImage(bi, null, null);
			file = makeTempTifFile(file);
			ImageOutputStream ios = ImageIO.createImageOutputStream(file);
			writer.setOutput(ios);
			writer.write(null, image, tiffWriteParam);
			ios.close();
			writer.dispose();
		} catch (Exception exc) {
			exc.printStackTrace();
		}
		return file;
	}

	/**
	 * 
	 * @param imageFile
	 * @return ä¸€ä¸ªä¸´æ—¶æ–‡ä»¶åxx0.tif
	 */
	private static File makeTempTifFile(File imageFile) {
		String path = imageFile.getPath();
		StringBuffer strB = new StringBuffer(path);
		strB.insert(path.lastIndexOf('.'), 0);
		return new File(strB.toString().replaceFirst("(?<=\\.)(\\w+)$", "tif"));
	}

	/**
	 * æImageè½¬æ¢æˆBufferedImage
	 * 
	 * @param image
	 * @return bufferedImage
	 */
	public static BufferedImage changeImageToBufferedImage(Image image) {
		BufferedImage bufferedImage = new BufferedImage(image.getWidth(null),
				image.getHeight(null), BufferedImage.TYPE_INT_RGB);
		Graphics2D g = bufferedImage.createGraphics();
		g.drawImage(image, 0, 0, null);
		return bufferedImage;
	}

	/**
	 * æimageProducerè½¬æ¢æˆbufferedImage
	 * 
	 * @param ImageProducer
	 * @return BufferedImage
	 */
	public static BufferedImage changeImageProducerToBufferedImage(
			ImageProducer imageProducer) {
		return changeImageToBufferedImage(Toolkit.getDefaultToolkit()
				.createImage(imageProducer));
	}

	/**
	 * æBufferedImageè½¬æ¢æˆbyte[]
	 * 
	 * @param bufferedImage
	 * @return byte[]
	 */
	public static byte[] changeBufferedImageToByteArray(BufferedImage image) {
		WritableRaster raster = image.getRaster();
		DataBufferByte buffer = (DataBufferByte) raster.getDataBuffer();
		return buffer.getData();
	}

	/**
	 * æImageä¿å­˜ä¸ºtiffæ ¼å¼ç„å›¾åƒ
	 * 
	 * @param image
	 * @param filePath
	 *            ä¿å­˜è·¯å¾„
	 */
	public static void storeImageToTiff(Image image, String filePath) {
		BufferedImage bufferedImage = changeImageToBufferedImage(image);
		try {
			OutputStream outputStreamFile = new FileOutputStream(filePath);
			TIFFEncodeParam param = new TIFFEncodeParam();
			ImageEncoder imageEncoder = ImageCodec.createImageEncoder("tiff",
					outputStreamFile, param);
			imageEncoder.encode(bufferedImage);
			outputStreamFile.flush();
			outputStreamFile.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}

	/**
	 * æå…¶ä»–æ ¼å¼ç„å›¾åƒè½¬æˆtiff,æ ¼å¼ä¸ºxxx0.tif
	 * 
	 * @param imageFile
	 * @return imageFile
	 */
	public static File changeToTiff(File imageFile) {
		File tempFile = makeTempTifFile(imageFile);
		Image image = null;
		try {
			image = ImageIO.read(imageFile);
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
		storeImageToTiff(image, tempFile.getPath());
		return tempFile;
	}

	public void readTiffImageProperties(String inputTifImagePath) {
		Iterator readersIterator = ImageIO.getImageReadersByFormatName("tif");
		ImageReader imageReader = (ImageReader) readersIterator.next();
		ImageInputStream imageInputStream;
		try {
			imageInputStream = new FileImageInputStream(new File(
					inputTifImagePath));
			imageReader.setInput(imageInputStream, false, true);

			/* Take a input from a file */
			FileSeekableStream fileSeekableStream;
			fileSeekableStream = new FileSeekableStream(inputTifImagePath);

			/* create ImageDecoder to count your pages from multi-page tiff */
			ImageDecoder iDecoder = ImageCodec.createImageDecoder("tiff",
					fileSeekableStream, null);

			/* count the number of pages inside the multi-page tiff */
			int pageCount = iDecoder.getNumPages();

			/* use first for loop to get pages one by one */
			for (int page = 0; page < pageCount; page++) {
				/* get image metadata for each page */
				IIOMetadata imageMetadata = imageReader.getImageMetadata(page);

				/*
				 * The root of all the tags for this image is the IFD (Image
				 * File Directory). Get the IFD from where we can get all the
				 * tags for the image.
				 */
				TIFFDirectory ifd = TIFFDirectory
						.createFromMetadata(imageMetadata);

				/* Create a Array of TIFFField */
				TIFFField[] allTiffFields = ifd.getTIFFFields();

				/* use second for loop to get all field data */
				for (int i = 0; i < allTiffFields.length; i++) {
					TIFFField tiffField = allTiffFields[i];

					/* name of property */
					String nameOfField = tiffField.getTag().getName();

					/* Tag no. of the property (optional) */
					int numberOfField = tiffField.getTagNumber();

					/* Type of property (optional) */
					String typeOfField = TIFFField.getTypeName(tiffField
							.getType());

					/* Value of Property */
					String valueOfField = tiffField.getValueAsString(0);

					/* print it down as per your way */
					System.out.println((i + 1) + ". " + nameOfField + ", "
							+ numberOfField + ", " + typeOfField + ", "
							+ valueOfField);
				}
				/* just for separate between two page (image) property */
				System.out.println("======================================");
			}
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}
